﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Platform.Storage.Redis.Messages
{
	/// <summary>
	/// 
	/// </summary>
	public abstract class RedisMessageBase<TReply> : IRedisMessage
	{
		private const int MaxStringLength = 512 * 1024 * 1024;
		private const string EndOfArgument = "\r\n";
		private TReply reply;

		/// <summary>
		/// 
		/// </summary>
		/// <param name="channel"></param>
		/// <param name="command"></param>
		/// <param name="args"></param>
		protected RedisMessageBase( RedisMessageChannel channel, string command, string[] args )
		{
			this.Channel = channel;
			this.Command = command;
			this.Arguments = args ?? Enumerable.Empty<string>();
			this.ReplyStatus = RedisReplyStatus.None;
		}

		/// <summary>
		/// Obtém o comando enviado para o REDIS
		/// </summary>
		public string Command
		{ get; private set; }

		/// <summary>
		/// Obtém a lista de parâmetros enviados para o REDIS
		/// </summary>
		public IEnumerable<string> Arguments
		{ get; private set; }

		/// <summary>
		/// Canal de comunicação com o REDIS
		/// </summary>
		public RedisMessageChannel Channel
		{ get; private set; }

		/// <summary>
		/// Indica se o resposta já foi retornada
		/// </summary>
		public bool Received
		{ get; private set; }

		/// <summary>
		/// Obtém o tamanho da mensagem enviada pelo REDIS
		/// </summary>
		//public int ReplyLength
		//{ get; private set; }

		/// <summary>
		/// Obtém a resposta enviada pelo REDIS
		/// </summary>
		public RedisReplyStatus ReplyStatus
		{ get; protected set; }

		public TReply Reply
		{
			get
			{
				if ( !this.Received )
					this.WaitReceive();
				return this.reply;
			}
		}

		/// <summary>
		/// Realiza o processamento da resposta
		/// </summary>
		/// <param name="nextLine"></param>
		/// <returns></returns>
		protected abstract TReply GetReply( Func<RedisReplyLine> nextLine );

		#region IRedisMessage Members

		public virtual void ReadReply( TextReader reader )
		{
			int msgLen = 0;

			this.reply = this.GetReply(
				() =>
				{
					RedisReplyLine line = new RedisReplyLine( reader.ReadLine() );

					if ( line.Line != null )
						msgLen += line.Line.Length + 2;

					// Pega o status da primeira linha que foi retornada na resposta
					if ( this.ReplyStatus != RedisReplyStatus.None )
						this.ReplyStatus = line.Reply;

					switch ( line.Reply )
					{
						case RedisReplyStatus.Error:
							this.Channel.Stats.ErrorReceived( msgLen );
							throw new RedisException( line.Message );

						case RedisReplyStatus.CollectionItem:
							// Lê quantos caracteres possui o próximo item
							int nextLineLen = Convert.ToInt32( line.Message );

							// Está nulo no redis?
							if ( nextLineLen <= 0 )
								return new RedisReplyLine( RedisReplyStatus.CollectionItem, ( string )null );
							else
							{
								// Retorna o conteúdo da próxima linha
								char[] nextLine = new char[ nextLineLen + 2 ];
								int read = reader.ReadBlock( nextLine, 0, nextLine.Length );
								msgLen += nextLine.Length;

								System.Diagnostics.Trace.Assert( read == nextLine.Length );

								return new RedisReplyLine( RedisReplyStatus.CollectionItem, new String( nextLine, 0, nextLineLen ) );
							}
						default:
							return line;
					}
				} );

			this.Channel.Stats.MessageReceived( msgLen );
			this.Received = true;
		}

		public virtual string ToWireMessage()
		{
			return this.ToString();
		}

		#endregion


		public override string ToString()
		{
			if ( String.IsNullOrEmpty( this.Command ) )
				return String.Empty;
			else
			{
				StringBuilder msg = new StringBuilder();

				this.WriteHeader( msg );
				this.WriteArguments( msg );

				return msg.ToString();
			}
		}

		public void WaitReceive()
		{
			this.Channel.WaitReplyOf( this );
		}

		/// <summary>
		/// Motna o cabeçalho e o comando para o redis
		/// </summary>
		/// <param name="command"></param>
		/// <param name="parametersCount"></param>
		protected void WriteHeader( StringBuilder msg )
		{
			int len = this.Arguments.Count() + 1;

			// Quantidade de parâmetros
			msg.Append( '*' ).Append( len ).Append( EndOfArgument );

			// Informa o comando
			this.WriteArgument( msg, this.Command );
		}

		protected void WriteArguments( StringBuilder msg )
		{
			foreach ( var arg in this.Arguments )
				this.WriteArgument( msg, arg );
		}

		/// <summary>
		/// Monta os argumentos
		/// </summary>
		/// <param name="arg"></param>
		protected void WriteArgument( StringBuilder msg, string arg )
		{
			int len = -1;
			bool hasValue = !String.IsNullOrEmpty( arg );
			string encodedArg = null;

			if ( hasValue )
			{
				//encodedArg = RedisUtility.Encode( arg );
				encodedArg = arg;
				len = encodedArg.Length;
				if ( len >= MaxStringLength )
					throw new InvalidOperationException();
			}

			// Envia o tamanho da string
			msg.Append( '$' ).Append( len ).Append( EndOfArgument );

			// Envia a string
			if ( hasValue )
				msg.Append( encodedArg ).Append( EndOfArgument );
				//msg.AppendEncoded( arg ).Append( EndOfArgument );
				//msg.Append( arg ).Append( EndOfArgument );
		}
	}
}
